/* Soubor je ulozen v kodovani UTF-8.
 * Kontrola kódování: Příliš žluťoučký kůň úpěl ďábelské ódy.
 */
package cz.pecinovsky.english.pojj.gui_obr;

import java.awt.BorderLayout;
import java.awt.Point;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;

import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;



/*******************************************************************************
 * Instance třídy {@code GUI_OBR_2} přestavují grafické aplikace
 * pro testování práce s grafikou, obrázky a posluchači událostí.
 * <p>
 * Tato verze opustila předdefinované obrázky a umožňuje zadat vykreslení
 * libovolného obrázku v zadané složce. Navíc vylepšuje práci se žurnálem
 * úpravou ošetření události ukončení aplikace.
 *
 * @author    Rudolf PECINOVSKÝ
 * @version   0.00.000,  0.0.2006
 */
public class GUI_OBR_2
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Souřadnice, na které se aplikační okno přesune po spuštění GUI.
     *  To má smysl především při zobrazování na sekundárním monitoru. */
    private static final Point POINT_0 = new Point(0, 0);       //(-600, 0);



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** Složka, ve které se nacházejí soubory s obrázky. V této verzi
     *  je sice konstantní, ale v dalších ji umožníme nastavovat.  */
    private static String názevSložkyZdrojů;

    /** Množina přípon označujících formáty grafických souborů,
     *  který umí java nakreslit. */
    private static final Set<String> přípony = new HashSet<String>();



//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================

    static {
        try {
            názevSložkyZdrojů =
                new File(GUI_OBR_2.class.getResource("OBR").toURI()).
                         getCanonicalPath() + "\\";
        }catch(Exception ex) {
            throw new RuntimeException(
                    "Nepodařilo se připravit složky se zdroji",ex);
        }

    /** Protože mapa přípon je statickým atributem, je nutno ji naplnit
     *  statickou metodou v deklaraci nebo
     *  ve statickém inicializačním bloku - to je použito zde. */
        přípony.add("png");
        přípony.add("gif");
        přípony.add("jpg");
        přípony.add("jpeg");
    }



//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Aplikační okno. */
    private final JFrame frmOkno =
            new JFrame("Práce se soubory a obrázky - verze 5");



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    //Následující atributy jsou sice také konstantní,
    //ale budu-li je daklarovat jako proměnné,
    //nebudu je muset všechny inicializovat v konstruktoru

    private JLabel    lblObrázek;   //Návěští zobrazující vykreslovaný obrázek

    private JMenuBar  mnbNabídka;

    private JMenu             mnuSoubor;
    private JMenuItem         mniOtevři;
    private JCheckBoxMenuItem mniŽurnál;

    private JMenu     mnuNápověda;
    private JMenuItem mniOAplikaci;

    private File    file;
    private Writer  výstup;

    JPanel panelTlačítek;

    private Map<String,Tlačítko> tlačítka =
                                 new LinkedHashMap<String,Tlačítko>();



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří instanci aplikace přičemž konstrukce dále zesložitěla.
     */
    private GUI_OBR_2(File složka)
    {
        //Následující posunutí má smysl pouze tehdy,
        //cheme-li aplikační okno zobrazit na sekundárním monitoru
        frmOkno.setLocation(POINT_0);

        //Nelze použí jednoduché nastavení ukončení aplikace při stisku
        //zavíracího tlačítka, protože součástí ukončení aplikace
        //musí být i zavření případného souboru se žurnálem.
        frmOkno.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if(výstup != null) {
                    zavřiŽurnál();
                }
                System.exit(0);
            }
        });

        připravNabídku();           //Připraví hlavní nabídku
        připravVýzvu();             //Návěští s výzvou, co má uživatel dělat
        připravPanelObrázků();      //Načti obrázky ze zadané složky
        //Připrav tlačítka pro vyvolání jednotlivých obrázků
        připravTlačítka(zjistiSoubory(složka));

        //Aktivace aplikačního okna
        frmOkno.pack();             //Optimalizuje velikost vytvořeného okna
        frmOkno.setVisible(true);   //a zviditelní je
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================
//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================

    /***************************************************************************
     * Vrátí vektor všech souborů, které se nachází v zadané složce
     * a mají některou z přípon zadaných v atributu {@code přípony}.
     *
     * @param složka Složka, v níž se budou hledat soubory
     * @return Vektor zobrazitelných souborů v zadané složce
     */
    private static File[] zjistiSoubory(File složka)
    {
        File[] ret = složka.listFiles(new FilenameFilter() {
            //Filtr, jímž projdou pouze soubory s některou z přípon,
            //zadaných v atributu přípony, tj. soubory, které bude program
            //umět nakreslit jako obrázky
            @Override
            public boolean accept(File dir, String name) {
                String přípona = name.substring(name.lastIndexOf('.')+1)
                                     .toLowerCase();
                //Akceptujeme pouze soubory s příponami ze zadané množiny
                return přípony.contains(přípona);
            }
        });
        System.out.println("Nalezené soubory s obrázky:");
        for(File f : ret) {
            System.out.println("   " + f);
        }
        return ret;
    }


//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Připraví v aplikačním okně hlavní nabídku
     */
    private void připravNabídku()
    {
        mnbNabídka = new JMenuBar();    //Vytvoření prázdné nabídkové lišty
        připravNabídkuSoubor();         //Přidání požadovaných nabídek
        připravNabídkuNápověda();
        frmOkno.setJMenuBar(mnbNabídka);
    }


    /***************************************************************************
     * Připraví návěští, do nějž budou umísťovány obrázky
     * a přidá je do aplikačního okna.
     */
    private void připravPanelObrázků()
    {
        lblObrázek = new JLabel();
        frmOkno.add(lblObrázek, BorderLayout.CENTER);
    }


    /***************************************************************************
     * Připraví nabídku Nápověda.
     */
    private void připravNabídkuNápověda()
    {
        mnuNápověda = new JMenu("Nápověda");
        mnuNápověda.setMnemonic('N');
        mniOAplikaci = mnuNápověda.add("Nic ti nepovim");

        mnbNabídka.add(mnuNápověda);
    }


    /***************************************************************************
     * Připraví nabídku Soubor.
     */
    private void připravNabídkuSoubor()
    {
        //Připravíme položku (nabídku) v hlavní nabídce
        mnuSoubor = new JMenu("Soubor");     //Text položky nabídky
        mnuSoubor.setMnemonic('S');          //Klávesová zkratka
        mnbNabídka.add(mnuSoubor);

        //Pokračujeme položkami v právě přidané nabídce

        //Otevření souboru, do nějž budeme ukládat mniŽurnál
        mniOtevři = mnuSoubor.add("Otevři");
        mniOtevři.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                zadejSoubor();
            }
        });

        //Přepínač ovlivňující, zda se bude zaznamenávat
        mniŽurnál = new JCheckBoxMenuItem("Žurnál");
        mniŽurnál.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ie) {
                //Pokračování podle stavu zaškrtnutí
                if(mniŽurnál.getState()) {  //Budeme zaznamenávat
                    zapniŽurnál();
                }
//                else {
//                    zavřiŽurnál();
//                }
            }
        });
        mnuSoubor.add(mniŽurnál);
    }


    /***************************************************************************
     * Připraví panel s tlačítky.
     */
    private void připravTlačítka(File[] soubory)
    {
        panelTlačítek = new JPanel();
        //Společný posluchač pro všechna tlačítka
        ActionListener posluchač = new PosluchačTlačítka();

        //Pro každý zadaný soubor bude vytvořeno tlačítko
        for(File soubor : soubory) {
            Tlačítko tl = new Tlačítko(soubor);
            tlačítka.put(tl.název, tl);
            tl.tlačítko.addActionListener(posluchač);
            panelTlačítek.add(tl.tlačítko);
        }
        frmOkno.add(panelTlačítek, BorderLayout.SOUTH);
    }


    /***************************************************************************
     */
    private void připravVýzvu()
    {
        frmOkno.add(new JLabel("Zadej svůj názor"), BorderLayout.NORTH);
    }


    /***************************************************************************
     * Požádá uživatele o zadání souboru pro žurnál.
     */
    private void zadejSoubor()
    {
        final JFileChooser fc = new JFileChooser();
        //Vynuluje file pro případ, že by uživatel nic nezadal
        file = null;
        int ret = fc.showOpenDialog(frmOkno);
        if (ret == JFileChooser.APPROVE_OPTION) {
            //Pokud uživatel něco zadal, uloží informaci do proměnné file
            file = fc.getSelectedFile();
        }
    }


    /***************************************************************************
     * Otevře výstupní soubor, do kterého se bude ukládat žurnál.
     */
    private void zapniŽurnál() throws RuntimeException {
        //Není-li přpraven žádný mnuSoubor, připrav jej
        if (file == null) {
            zadejSoubor();
            if (file == null) {
                //Soubor se nepodařilo připravit =>
                // => zaznamenávat se nebude
                mniŽurnál.setState(false);
                return;
            }
        }
        try {
            //Připrav přísušný výstupní proud, budeme přidávat na konec
            Writer proud = new FileWriter(file, true);
            výstup = new BufferedWriter(proud);
//                výstup = new BufferedWriter(new FileWriter(file, true));
                výstup.write("\n" + new Date() + " - Začátek seance\n\n");
                výstup.flush();
        } catch (IOException exc) {
            throw new RuntimeException(
                    "\nNepodařilo se otevřít soubor", exc);
        }
    }


    /***************************************************************************
     * Zavře výstupní soubor, do kterého se ukládá žurnál.
     */
    private void zavřiŽurnál()
    {
        if(výstup != null) {
            try {
                výstup.write("\n" + new Date() + " - Konec seance");
                výstup.close();
            } catch(IOException exc) {
                throw new RuntimeException(
                    "\nSoubor " + výstup +
                    " se žurnálem se nepodařilo zavřít", exc);
            }
        }
    }



//== INTERNÍ DATOVÉ TYPY =======================================================

    /***************************************************************************
     * Instance třídy Tlačítko jsou jednoduché přepravky
     * sloužící ke združení souvisejících informací a objektů.
     */
    private class Tlačítko
    {
        String  název;
        JButton tlačítko;
        Icon    obrázek;

        Tlačítko(File soubor) {
            název = soubor.getName();
            název = název.substring(0, název.lastIndexOf('.'));
            tlačítko = new JButton(název);
            obrázek  =  new ImageIcon(soubor.getAbsolutePath());
        }
    }



    /***************************************************************************
     * Instance třídy PosluchačTlačítka představuje společného posluchače
     * všech tlačítek.
     * Je definována jako vnitřní, abychom mohli používat atributy instance.
     */
    private class PosluchačTlačítka implements ActionListener
    {
        /***********************************************************************
         * {@inheritDoc}
         * @param e Očekávaná událost
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            String zdroj = e.getActionCommand();
            if(!tlačítka.containsKey(zdroj)) {
                throw new RuntimeException("Neznámá událost");
            }
            Tlačítko tlačítko = tlačítka.get(zdroj);
            lblObrázek.setIcon(tlačítko.obrázek);
            if(mniŽurnál.getState()) {
                try {
                    výstup.write(new Date() + ": " + zdroj + "\n");
                    výstup.flush();
                } catch(IOException exc) {
                    throw new RuntimeException(
                        "\nChyba při výstupu do souboru " + výstup, exc);
                }
            }
            frmOkno.pack();
            lblObrázek.repaint();
        }
     }



//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda.
     *
     * @param args  Parametry příkazového řádku s názvem složky,
     *              v níž jsou umístěny soubory s obrázky
     */
     public static void test(String[] args) {
         File složka;
         if(args.length > 0) {
             složka = new File(args[0]);
         }
         else {
             složka = new File(GUI_OBR_2.názevSložkyZdrojů);
         }
         new GUI_OBR_2(složka);
     }


    /***************************************************************************
     * @param args  Parametry příkazového řádku s názvem složky,
     *              v níž jsou umístěny soubory s obrázky
     */
     public static void main(String[] args)  {  test(args);  }
}
